#include "soc_flash.h"
#include "main.h"
#include "log/logger.h"

#if defined STM32F0                  
#define FLASH_OP_TYPE       FLASH_TYPEPROGRAM_HALFWORD
#define FLASH_OP_DATA_SIZE  2
#define FLASH_OP_DATA_TYPE  uint16_t 
#elif defined STM32F1
#define FLASH_OP_TYPE       FLASH_TYPEPROGRAM_HALFWORD
#define FLASH_OP_DATA_SIZE  2
#define FLASH_OP_DATA_TYPE  uint16_t 
#elif defined STM32G4 || defined STM32F4
#define FLASH_OP_TYPE       FLASH_TYPEPROGRAM_DOUBLEWORD
#define FLASH_OP_DATA_SIZE  8
#define FLASH_OP_DATA_TYPE  uint64_t 
#endif   

uint32_t SOC_Flash::m_baseAddr = 0;

static inline FLASH_OP_DATA_TYPE ReadOne(uint32_t address)
{
    return *(volatile FLASH_OP_DATA_TYPE *)address;
}

void SOC_Flash::SetBaseAddress(uint32_t address)
{
    m_baseAddr = address;
}

bool SOC_Flash::Write(uint32_t address, uint8_t *buf, int length)
{
    TryConvertAddress(address);
    
    if(!CheckAddressAndLog(address))
        return false;
    if(buf == nullptr)
        return false;
    if(length <= 0)
        return false;

    WriteToFlashImpl(address, buf, length);

    return true;
}

bool SOC_Flash::Read(uint32_t address, uint8_t *buf, int length)
{
    TryConvertAddress(address);

    if(!CheckAddressAndLog(address))
        return false;
    if(buf == nullptr)
        return false;
    if(length <= 0)
        return false;

    ReadFromFlashImpl(address, buf, length);

    return true;
}

uint32_t SOC_Flash::GetSpaceSize(uint32_t startAddress)
{
    return (GetFlashEndAddress() - startAddress);
}

uint32_t SOC_Flash::GetSpaceSizeByBaseAddress()
{
    if(m_baseAddr == 0)
        return 0;
    return GetSpaceSize(m_baseAddr);
}

uint32_t SOC_Flash::GetFlashSize()
{
    return FLASH_SIZE;//byte
}

uint32_t SOC_Flash::GetFlashStartAddress()
{
    return FLASH_BASE;
}

uint32_t SOC_Flash::GetFlashEndAddress()
{
    static uint32_t val = FLASH_BASE + GetFlashSize();
    return val;
}

uint32_t SOC_Flash::GetFlashPageSize()
{
    return FLASH_PAGE_SIZE;
}

uint32_t SOC_Flash::ConvertToFlashAddress(uint32_t virual_address)
{
    return FLASH_BASE + virual_address;
}

bool SOC_Flash::CheckFlashAddress(uint32_t address)
{
    return address >= GetFlashStartAddress() || address < GetFlashEndAddress();
}

void SOC_Flash::TryConvertAddress(uint32_t &address)
{
    if(m_baseAddr > 0)
    {
        address += m_baseAddr;
    }
}

bool SOC_Flash::CheckAddressAndLog(uint32_t address)
{
    if(!CheckFlashAddress(address))
    {
        LogDebug()<<"address error. "<<address;
        return false;
    }

    //[FLASH_OP_DATA_SIZE] 字节对齐
    if(address % FLASH_OP_DATA_SIZE != 0)
    {
        LogDebug()<<"address must be"<<FLASH_OP_DATA_SIZE<<"bytes-aligned. "<<address;
        return false;
    }

    return true;
}

bool SOC_Flash::CheckBufferLengthAndLog(int length)
{
    if(length <= 0 || length > GetFlashPageSize())
    {
        LogDebug()<<"length length error";
        return false;
    }

    return true;
}

bool SOC_Flash::ReadFromFlashImpl(uint32_t address, uint8_t *buffer, int length)
{
    FW_ASSERT(buffer != nullptr || length > 0);
    FW_ASSERT(CheckFlashAddress(address));

    if(!CheckBufferLengthAndLog(length))
        return false;

    int size = length / FLASH_OP_DATA_SIZE;
    int remains = length % FLASH_OP_DATA_SIZE;

    if(size > 0)
    {
        FLASH_OP_DATA_TYPE *dataPtr = (FLASH_OP_DATA_TYPE*)buffer;
        for(int i = 0; i < size; i++)
        {
            auto data = ReadOne(address);
                
            dataPtr[i] = data;
            address += FLASH_OP_DATA_SIZE;
        }
    }

    if(remains > 0) 
    {
        auto data = ReadOne(address);
        int startIndex = size * FLASH_OP_DATA_SIZE;

        for(int i = 0; i < remains; i++)
        {
            buffer[startIndex + i] = data & 0XFF;
            data >>= 8;
        }
    }

    return true;
}

bool SOC_Flash::WriteToFlashImpl(uint32_t address, uint8_t *buffer, int length)
{
    FW_ASSERT(buffer != nullptr || length > 0);
    FW_ASSERT(CheckFlashAddress(address));

    if(!CheckBufferLengthAndLog(length))
        return false;

    int size = length / FLASH_OP_DATA_SIZE;
    int remains = length % FLASH_OP_DATA_SIZE;
    
    HAL_FLASH_Unlock();     /* FLASH解锁 */

    EraseSector(address);

    FLASH_OP_DATA_TYPE *dataPtr = (FLASH_OP_DATA_TYPE*)buffer;
    for(int i = 0; i < size; i++)
    {
        HAL_FLASH_Program(FLASH_OP_TYPE, address, dataPtr[i]);
        address += FLASH_OP_DATA_SIZE;
    }

    if(remains > 0)
    {
        FLASH_OP_DATA_TYPE data = 0;
        int startIndex = size * FLASH_OP_DATA_SIZE;
        
        for(int i = 0; i < remains; i++)
        {
            data |= (buffer[startIndex + i] << i);
        }
        HAL_FLASH_Program(FLASH_OP_TYPE, address, data);
    }

    HAL_FLASH_Lock(); /* 上锁 */

    return true;
}

bool SOC_Flash::EraseSector(uint32_t address)
{
    FLASH_EraseInitTypeDef flash_eraseop;

    int page = (address - GetFlashStartAddress()) / GetFlashPageSize();
    uint32_t pageError;

    flash_eraseop.TypeErase = FLASH_TYPEERASE_PAGES;     /* 选择页擦除 */
    flash_eraseop.Banks = FLASH_BANK_1;
    flash_eraseop.NbPages = 1;
    flash_eraseop.Page = page;  /* 要擦除的扇区 */
    HAL_FLASHEx_Erase(&flash_eraseop, &pageError);

    return true;
}
